﻿namespace OnlineVideos.RingToneEditor
{
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.InteropServices;

   
    public class WaveFormat
    {
        protected WaveFormatEncoding waveFormatTag;
        protected short channels;
        protected int sampleRate;
        protected int averageBytesPerSecond;
        protected short blockAlign;
        protected short bitsPerSample;
        protected short extraSize;
        public WaveFormat() : this(0xac44, 0x10, 2)
        {
        }

        public WaveFormat(int sampleRate, int channels) : this(sampleRate, 0x10, channels)
        {
        }

        public int ConvertLatencyToByteSize(int milliseconds)
        {
            int num = (int) ((((double) this.AverageBytesPerSecond) / 1000.0) * milliseconds);
            if ((num % this.BlockAlign) != 0)
            {
                num = (num + this.BlockAlign) - (num % this.BlockAlign);
            }
            return num;
        }

        public static WaveFormat CreateCustomFormat(WaveFormatEncoding tag, int sampleRate, int channels, int averageBytesPerSecond, int blockAlign, int bitsPerSample)
        {
            return new WaveFormat { waveFormatTag = tag, channels = (short) channels, sampleRate = sampleRate, averageBytesPerSecond = averageBytesPerSecond, blockAlign = (short) blockAlign, bitsPerSample = (short) bitsPerSample, extraSize = 0 };
        }

        public static WaveFormat CreateALawFormat(int sampleRate, int channels)
        {
            return CreateCustomFormat(WaveFormatEncoding.ALaw, sampleRate, channels, sampleRate * channels, 1, 8);
        }

        public static WaveFormat CreateMuLawFormat(int sampleRate, int channels)
        {
            return CreateCustomFormat(WaveFormatEncoding.MuLaw, sampleRate, channels, sampleRate * channels, 1, 8);
        }

        public WaveFormat(int rate, int bits, int channels)
        {
            if (channels < 1)
            {
                throw new ArgumentOutOfRangeException("Channels must be 1 or greater", "channels");
            }
            this.waveFormatTag = WaveFormatEncoding.Pcm;
            this.channels = (short) channels;
            this.sampleRate = rate;
            this.bitsPerSample = (short) bits;
            this.extraSize = 0;
            this.blockAlign = (short) (channels * (bits / 8));
            this.averageBytesPerSecond = this.sampleRate * this.blockAlign;
        }

        public static WaveFormat CreateIeeeFloatWaveFormat(int sampleRate, int channels)
        {
            WaveFormat format=null;
            return new WaveFormat { waveFormatTag = WaveFormatEncoding.IeeeFloat, channels = (short) channels, bitsPerSample = 0x20, sampleRate = sampleRate, blockAlign = (short) (4 * channels), averageBytesPerSecond = sampleRate * format.blockAlign, extraSize = 0 };
        }

        public static WaveFormat MarshalFromPtr(IntPtr pointer)
        {
            WaveFormat format = (WaveFormat) Marshal.PtrToStructure(pointer, typeof(WaveFormat));
            switch (format.Encoding)
            {
                case WaveFormatEncoding.Pcm:
                    format.extraSize = 0;
                    return format;

                case WaveFormatEncoding.Adpcm:
                    return (AdpcmWaveFormat) Marshal.PtrToStructure(pointer, typeof(AdpcmWaveFormat));

                case WaveFormatEncoding.Gsm610:
                    return (Gsm610WaveFormat) Marshal.PtrToStructure(pointer, typeof(Gsm610WaveFormat));

                case WaveFormatEncoding.Extensible:
                    return (WaveFormatExtensible) Marshal.PtrToStructure(pointer, typeof(WaveFormatExtensible));
            }
            if (format.ExtraSize > 0)
            {
                format = (WaveFormatExtraData) Marshal.PtrToStructure(pointer, typeof(WaveFormatExtraData));
            }
            return format;
        }

        //public static IntPtr MarshalToPtr(WaveFormat format)
        //{
        //    //IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(format));
        //    //Marshal.StructureToPtr(format, ptr, false);
        //    //return ptr;
        //}

        public static WaveFormat FromFormatChunk(BinaryReader br, int formatChunkLength)
        {
            WaveFormatExtraData data = new WaveFormatExtraData();
            data.ReadWaveFormat(br, formatChunkLength);
            data.ReadExtraData(br);
            return data;
        }

        private void ReadWaveFormat(BinaryReader br, int formatChunkLength)
        {
            if (formatChunkLength < 0x10)
            {
                //throw new ApplicationException("Invalid WaveFormat Structure");
            }
            this.waveFormatTag = (WaveFormatEncoding) br.ReadUInt16();
            this.channels = br.ReadInt16();
            this.sampleRate = br.ReadInt32();
            this.averageBytesPerSecond = br.ReadInt32();
            this.blockAlign = br.ReadInt16();
            this.bitsPerSample = br.ReadInt16();
            if (formatChunkLength > 0x10)
            {
                this.extraSize = br.ReadInt16();
                if (this.extraSize != (formatChunkLength - 0x12))
                {
                    Debug.WriteLine("Format chunk mismatch");
                    this.extraSize = (short) (formatChunkLength - 0x12);
                }
            }
        }

        public WaveFormat(BinaryReader br)
        {
            int formatChunkLength = br.ReadInt32();
            this.ReadWaveFormat(br, formatChunkLength);
        }

        public override string ToString()
        {
            switch (this.waveFormatTag)
            {
                case WaveFormatEncoding.Pcm:
                case WaveFormatEncoding.Extensible:
                    return string.Format("{0} bit PCM: {1}kHz {2} channels", this.bitsPerSample, this.sampleRate / 0x3e8, this.channels);
            }
            return this.waveFormatTag.ToString();
        }

        public override bool Equals(object obj)
        {
            WaveFormat format = obj as WaveFormat;
            return ((format != null) && (((((this.waveFormatTag == format.waveFormatTag) && (this.channels == format.channels)) && ((this.sampleRate == format.sampleRate) && (this.averageBytesPerSecond == format.averageBytesPerSecond))) && (this.blockAlign == format.blockAlign)) && (this.bitsPerSample == format.bitsPerSample)));
        }

        public override int GetHashCode()
        {
            return ((((((int) (this.waveFormatTag ^ ((WaveFormatEncoding) this.channels))) ^ this.sampleRate) ^ this.averageBytesPerSecond) ^ this.blockAlign) ^ this.bitsPerSample);
        }

        public WaveFormatEncoding Encoding
        {
            get
            {
                return this.waveFormatTag;
            }
        }
        public virtual void Serialize(BinaryWriter writer)
        {
            writer.Write((int) (0x12 + this.extraSize));
            writer.Write((short) this.Encoding);
            writer.Write((short) this.Channels);
            writer.Write(this.SampleRate);
            writer.Write(this.AverageBytesPerSecond);
            writer.Write((short) this.BlockAlign);
            writer.Write((short) this.BitsPerSample);
            writer.Write(this.extraSize);
        }

        public int Channels
        {
            get
            {
                return this.channels;
            }
        }
        public int SampleRate
        {
            get
            {
                return this.sampleRate;
            }
        }
        public int AverageBytesPerSecond
        {
            get
            {
                return this.averageBytesPerSecond;
            }
        }
        public virtual int BlockAlign
        {
            get
            {
                return this.blockAlign;
            }
        }
        public int BitsPerSample
        {
            get
            {
                return this.bitsPerSample;
            }
        }
        public int ExtraSize
        {
            get
            {
                return this.extraSize;
            }
        }
    }
}

